import Vue from "../core/index.js";
import { isReverseTag } from "../utils/index.js";

export default function patch(oldVNode, vnode) {
    if (oldVNode && !vnode) { // 老的vnode存在 新的vnode不存在 这种情况是组件被销毁
        return
    }
    // 子组件没有$el
    if (!oldVNode) {
        // 子组件首次渲染 没有parent
        createElem(vnode)
        return
    } else if (oldVNode.nodeType) { // 真实节点首次渲染
        const parent = oldVNode.parentNode;
        const nextNode = oldVNode.nextSibling; // 后面的兄弟节点
        // 解析新的vnode 替换老的vnode
        createElem(vnode, parent, nextNode);
        // 新的vnode生成完毕 删除老的vnode
        parent.removeChild(oldVNode)
    } else {
        // 更新操作 打补丁阶段...
        console.log("update page...")
        // 进入更新阶段 老的vnode 与 新的vnode进行比较
        patchVnode(oldVNode, vnode)
    }
}
/**
 * 将vnode变成真实节点函数
 * @param {*} vnode 虚拟节点
 * @param {*} parent 父节点
 * @param {*} nextNode 
 */
function createElem(vnode, parent, nextNode) {
    // 渲染子组件的时候需要用到父节点 将渲染的子组件真实节点追加进父节点
    vnode.parent = parent
    const { tag, attr, text, children, context } = vnode
    // 创建自定义组件 如果不是组件 会走后面的流程 (标签存在并且不是原生标签 而是一个自定义组件)
    if (tag && !isReverseTag(tag)) {
        createComponent(vnode)
        return
    }
    if (text) { // 文本节点
        // console.log(text, '文本ast')
        vnode.elm = createTextNode(text, context)
    } else { // 元素节点
        vnode.elm = document.createElement(tag)
        setAttributes(attr, vnode)
        for (let i = 0, len = children.length; i < len; i++) {
            createElem(children[i], vnode.elm)
        }
    }
    // 全部递归创建完成之后 插入到页面中
    if (nextNode) { // 存在兄弟元素
        parent.insertBefore(vnode.elm, nextNode)
    } else { // 不存在兄弟元素 可能是边界节点 直接追加
        parent && parent.appendChild(vnode.elm)
    }
}
/**
 * 创建自定义组件
 * @param {*} vnode 虚拟DOM
 */
function createComponent(vnode) {
    const { context: { $options: { components } }, tag } = vnode
    // 拿到组件的模版 创建子组件的vue实例
    const compInstance = new Vue(components[tag])
    // 这里需要手动挂载子组件 因为子组件没有$el
    compInstance.$mount()
    // 这里的_vnode是_update中在Vue实例上保存的_vnode
    // console.log(compInstance._vnode)
    // 拿到当前组件的父节点 将当前编译好的组件 追加进父节点
    vnode.parent.appendChild(compInstance._vnode.elm)
}

function createTextNode(textNode, context) {
    // 表达式文本
    let node = null
    if (textNode.expression) {
        // 替换马斯坦语法
        node = document.createTextNode(textNode.text.replace(/{{(.+?)}}/, ($, $1) => renderRealData(context, $1.trim())))
    } else {
        // 普通文本
        node = document.createTextNode(textNode.text)
    }
    return node
}
/**
 * 马斯坦语法转换为真实数据
 * @param {*} context 
 * @param {*} data 
 */
function renderRealData(context, data) {
    data = data.split(".")
    let index = 1, curr = context[data[0]], len = data.length
    while (index < len) {
        curr = curr[data[index]]
        index++
    }
    return typeof curr === 'object' ? JSON.stringify(curr) : curr
}
/**
 * 设置属性
 * @param {*} attr 
 * @param {*} vnode 
 */
function setAttributes(attr, vnode) {
    const { tag, context, elm } = vnode
    // return 
    for (let name in attr) {
        if (name == "vModel") {
            setVModel(tag, elm, attr.vModel.value, context)
        } else if (name === "vBind") {
            // console.log("bind property:", attr[name])
            setVBind(attr[name], elm, context)
        }
        else if (name === "vOn") {
            // console.log(name, attr[name])
            setVOn(attr[name], elm, context)
        }
        else {
            if (!/@(.*)|v-on:(.*)|v-model|v-bind:(.*)|:(.*)/.test(name)) {
                elm.setAttribute(name, attr[name])
            }
        }
    }
}
/* 数据双向绑定 */
function setVModel(tag, node, value, context) {
    if (tag === 'input') {
        if (node.type === 'text') {
            node.value = context[value]
            node.addEventListener('input', (e) => context[value] = e.target.value)
        } else if (node.type === 'checkbox') {
            node.checked = context[value]
            node.addEventListener('change', (e) => {
                context[value] = e.checked;
            })
        }
    } else if (tag === 'select' || tag === "textarea") {
        // 此时option还没有创建 需要异步地去更新select的值
        Promise.resolve().then((res) => node.value = context[value])
        node.addEventListener('change', (e) => context[value] = e.target.value)
    }
}
/* 绑定属性 */
function setVBind(attrs, node, context) {
    for (let key in attrs) {
        node.setAttribute(key, context[attrs[key]])
    }
}
/* 绑定事件 */
function setVOn(events, node, context) {
    for (let event in events) {
        node.addEventListener(event, function (...args) {
            // console.log(context.$methods, events[event])
            context.$methods[events[event]].apply(context, args)
        })
    }
}

/* 更新节点 */
function patchVnode(oldVnode, vnode) {
    // 两个节点相等不用更新
    if (oldVnode === vnode) return
    // 将老节点的真实节点 保存到新的虚拟节点身上 方便后续进行dom的操作
    vnode.elm = oldVnode.elm
    const child = vnode.children, oldChild = oldVnode.children
    if (!vnode.text) {
        // 说明是一个元素节点
        if (child && oldChild) {
            updateChildren(child, oldChild)
        } else if (child) {
            // 创建新的节点
        } else if (oldChild) {
            // 删除旧的节点
        }
    } else {
        // 只有是表达式的时候文本节点才会发生变更
        if (vnode.text.expression) {
            const { context: vm } = vnode
            // 读取data新数据 与旧的真实节点渲染的数据做比较 数据不同再进行修改
            const value = vnode.text.text.replace(/{{(.*)}}/g, function ($, $1) {
                return renderRealData(vm, $1.trim())
            })
            // 值不一样才更新
            // console.log('值比较：', value, "和", oldVnode.elm.textContent)
            if (value !== oldVnode.elm.textContent) {
                oldVnode.elm.textContent = value
            }
        }
    }
}

/* 更新孩子节点 */
function updateChildren(child, oldChild) {
    // 找出子节点的不同点 通过四种假设来提高diff性能
    let newStartIdx = 0, newEndIdx = child.length - 1, oldStartIdx = 0, oldEndIdx = oldChild.length - 1;
    while (newStartIdx <= newEndIdx || oldStartIdx <= oldEndIdx) {
        const oldStartNode = oldChild[oldStartIdx],
            oldEndNode = oldChild[oldEndIdx],
            newStartNode = child[newStartIdx],
            newEndNode = child[newEndIdx];
        if (sameVnode(oldStartNode, newStartNode)) {
            patchVnode(oldStartNode, newStartNode)
            oldStartIdx++
            newStartIdx++
        } else if (sameVnode(oldStartNode, newEndNode)) {
            patchVnode(oldStartNode, newEndNode)
            /* 执行插入操作 */
            oldStartNode.elm.parentNode.insertBefore(oldStartNode.elm, oldChild[newEndIdx].elm.nextSibling)
            newEndIdx--
            oldStartIdx++
        } else if (sameVnode(oldEndNode, newStartNode)) {
            patchVnode(oldEndNode, newStartNode)
            /* 将老节点插入到新的位置 */
            oldEndIdx.elm.parentNode.insertBefore(oldEndIdx.elm, oldChild[newStartIdx].elm)
            oldEndIdx--
            newStartIdx++
        } else if (sameVnode(oldEndNode, newEndNode)) {
            patchVnode(oldEndNode, newEndNode)
            oldEndIdx--
            newEndIdx--
        }
    }
    if (newStartIdx < newEndIdx) {
        // 新增了节点
    } else if (oldStartIdx < oldEndIdx) {
        // 删除了节点
    }
}

/* 比较两个虚拟dom是否为同一个 */
function sameVnode(oldVnode, vnode) {
    return oldVnode.key === vnode.key && oldVnode.tag === vnode.tag
}